/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.zhiqim.git.command.BranchCreateCommand;
import org.zhiqim.git.command.BranchDeleteCommand;
import org.zhiqim.git.command.BranchListCommand;
import org.zhiqim.git.command.BranchRenameCommand;
import org.zhiqim.git.command.CommitCommand;
import org.zhiqim.git.command.LogCommand;
import org.zhiqim.git.command.TagCommand;
import org.zhiqim.git.command.TagDeleteCommand;
import org.zhiqim.git.command.TagListCommand;
import org.zhiqim.git.command.TagRenameCommand;
import org.zhiqim.git.objer.ObjectLoader;
import org.zhiqim.git.objer.model.Tree;
import org.zhiqim.git.objer.model.TreeEntry;
import org.zhiqim.git.objer.objectid.ObjectId;
import org.zhiqim.git.objer.objectid.RevBlob;
import org.zhiqim.git.objer.objectid.RevCommit;
import org.zhiqim.git.objer.objectid.RevObject;
import org.zhiqim.git.objer.objectid.RevTree;
import org.zhiqim.git.refer.Ref;
import org.zhiqim.git.refer.RefAdvertiser;
import org.zhiqim.git.refer.RefRenamer;
import org.zhiqim.git.refer.RefUpdater;
import org.zhiqim.git.util.Gits;

import org.zhiqim.kernel.annotation.AnNullable;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Files;
import org.zhiqim.kernel.util.Validates;

/**
 * Git仓库主类
 * 
 * 1.只有元数据的裸仓库
 * zhiqim.git
 *      branches/                           分枝
 *      hooks/                              挂勾
 *      logs/                               日志
 *      info/                               信息
 *      objects/                            对象
 *          info/
 *          pack/
 *      refs/                               标记和头
 *          heads/                          分枝引用
 *          remotes/                        远程引用
 *          tags/                           标签引用
 *      config                              配置文件
 *      HEAD                                头文件
 * 裸仓库下${gitDir}=${rootDir}
 *      
 * 2.有工作树和元数据的仓库
 * zhiqim.git
 *      .git
 *          branches/                       分枝
 *          hooks/                          挂勾
 *          logs/                           日志
 *          info/                           信息
 *          objects/                        对象目录
 *              info/
 *              pack/
 *          refs/                           引用目录
 *              heads/                      分枝引用
 *              remotes/                    远程引用
 *              tags/                       标签引用
 *          config                          配置文件
 *          HEAD                            头文件
 *          index                           索引文件，裸仓库没有该文件
 *      新建文件夹
 *          1.txt
 *      新建文件.txt
 * 非裸仓库下${gitDir}=${rootDir}/.git
 */
public class Git implements GitConstants
{
    private final File gitMetaDir;          //Git元数据目录
    private final File gitWorkDir;          //Git工作目录
    private final GitConfig config;         //Git配置文件
    private final GitRefer refer;           //Git引用管理器
    private final GitObjer objer;           //Git对象管理器
    
    /************************************************************************************/
    //构造函数
    /************************************************************************************/
    
    /**
     * Git构造函数，只提供gitMetaDir，一般用于裸仓库
     * 
     * @param directory         Git目录
     * @param bare              是否裸仓库
     * @throws IOException      可能的异常
     */
    public Git(final File directory, boolean bare) throws IOException
    {
        Asserts.asserts(directory != null, "Git目录必须提供");
        
        //1.设定目录
        if (bare)
        {//裸仓库情况时无工作目录，指定的目录为元数据目录
            this.gitWorkDir = null;
            this.gitMetaDir = directory;
        }
        else
        {//非裸仓库，指定目录为工作目录，元数据库目录为工作目录下.git文件夹
            this.gitWorkDir = directory;
            this.gitMetaDir = new File(gitWorkDir, _GIT);
        }

        //2.加载仓库配置
        config = new GitConfig(new File(this.gitMetaDir, "config"));
        config.load();

        //3.初始化引用目录
        refer = new GitRefer(this);
        
        //4.初始化对象数据库
        objer = new GitObjer(this);
        if (objer.exists())
        {
            int repositoryFormatVersion = config.getRepositoryFormatVersion();
            if (repositoryFormatVersion != 0)
            {
                throw new IOException("未知的仓库格式版本[" + repositoryFormatVersion + "]");
            }
            
            objer.initIndexMap();
        }
    }

    /************************************************************************************/
    //创建&关闭Git仓库
    /************************************************************************************/
    
    /**
     * 创建Git仓库，执行create前请选设置好GitMetaDir或GitWorkDir等参数
     * 
     * @param name          创建者名称
     * @param email         创建者邮箱
     * @return              当前Git仓库
     * @throws IOException  可能的异常
     */
    public synchronized Git create(String name, String email) throws Exception
    {
        Asserts.assertState(!config.exists(), "Git仓库已存在：" + gitMetaDir);
        
        //1.创建Git元数据目录和分枝目录
        Files.mkDirectory(gitMetaDir);
        Files.mkDirectory(new File(gitMetaDir, "branches"));
        
        //2.创建引用管理器和对象管理器
        refer.create();
        objer.create();

        //3.关联HEAD到master分枝
        RefUpdater head = newUpdater(HEAD);
        head.disableRefLog();
        head.link(P_REFS_HEADS_ + MASTER);
        
        //4.初始化一次提交，创建master分枝
        commit().setMessage("主分枝初始化")
            .setAuthor(name, email)
            .setCommitter(name, email)
            .call();

        //5.设置仓库默认配置并保存
        config.setRepositoryFormatVersion(0)
              .setFileMode(true)
              .setBare(isBare())
              .setLogAllRefUpdates(!isBare())
              .setAutoCRLF(false)
              .save();
        
        return this;
    }
    
    /** 关闭Git仓库 */
    public void close()
    {
        objer.close();
        refer.close();
    }
    
    /************************************************************************************/
    //Git仓库基础数据
    /************************************************************************************/
    
    /** 是否裸仓库 */
    public boolean isBare()
    {
        return gitWorkDir == null;
    }
    
    /** 获取工作目录 */
    @AnNullable //裸仓库时没有索引文件
    public File getWorkDir()
    {
        return gitWorkDir;
    }
    
    /** 获取元数据库目录 */
    public File getMetaDir()
    {
        return gitMetaDir;
    }
    
    /** 获取配置信息 */
    public GitConfig getConfig()
    {
        return config;
    }
    
    /** 获取引用管理器 */
    public GitRefer getRefer()
    {
        return refer;
    }
    
    /** 获取对象管理器 */
    public GitObjer getObjer()
    {
        return objer;
    }
    
    /** 获取对象数据库的目录 */
    public File getObjectsDirectory()
    {
        return objer.getDirectory();
    }

    /************************************************************************************/
    //Git加载对象
    /************************************************************************************/
    
    /**
     * 通过对象编号或引用名称加载到简单的ObjectId
     * 
     * @param idOrName          校对字符串
     * @return                  ObjectId
     * @throws IOException      可能的异常
     */
    public ObjectId resolveIdOrName(String idOrName) throws IOException
    {
        //1.判断是不是标准校对码
        if (ObjectId.isObjectId(idOrName))
            return ObjectId.parse(idOrName);
        
        //2.认为是引用名称
        return refer.getObjectId(idOrName);
    }
    
    /**
     * 通过对象编号或引用名称加载校对对象
     * 
     * @param idOrName          校对字符串
     * @param clazz             类结构
     * @return                  RevObject或其子类
     * @throws IOException      可能的异常
     */
    public <T extends RevObject> T resolve(String idOrName, Class<T> clazz) throws IOException
    {
        ObjectId oid = resolveIdOrName(idOrName);
        if (oid == null)
            return null;
        
        return resolve(oid, clazz);
    }
    
    /**
     * 获取提交引用，指令对象编号
     * 
     * @param objectId      对象编号
     * @return RevCommit    类结构
     */
    @SuppressWarnings("unchecked")
    public <T extends RevObject> T resolve(ObjectId oid, Class<T> clazz) throws IOException
    {
        GitWalker walker = new GitWalker(this);
        if (clazz == RevObject.class)
        {//any
            return (T)walker.parse(oid);
        }
        else if (clazz == RevTree.class)
        {//tree
            return (T)walker.parseTree(oid);
        }
        else if (clazz == RevCommit.class)
        {//commit
            return (T)walker.parseCommit(oid);
        }
        else if (clazz == RevBlob.class)
        {//blob
            return (T)walker.parseBlob(oid);
        }
        else
        {//其他的不支持
            throw Asserts.exception("不支持的类型");
        }
    }
    
    /**
     * 解析Git校对字符串表达式，得到一个对象编码
     * 1、标准校对码（SHA-1），如：
     * 2、引用名称（refs/...）
     * 3、引用的路径（ref~n）               //TODO 暂不支持
     * 4、引用的第n个版本（ref@{n}）        //TODO 暂不支持
     * 5、引用的第几个父引用（ref^n）       //TODO 暂不支持
     * 6、引用的树引用（ref^{tree}）
     * 7、引用的文件引用（ref^{blob}）
     * 8、引用的提交日志（ref^{commit}）
     * 
     * @param revision          校对字符串
     * @return                  ObjectId
     * @throws IOException      可能的异常
     */
    public ObjectId resolve(final String revision) throws IOException
    {
        //1.判断是不是标准校对码
        if (ObjectId.isObjectId(revision))
            return ObjectId.parse(revision);
        
        //2.判断是否有“/”，认为是引用名称
        if (revision.indexOf("/") != -1)
            return refer.getObjectId(revision);
        
        //3.判断是否有“~”
        int ind = revision.indexOf("~");
        if (ind != -1)
        {
            throw Asserts.exception("暂不支持[~]表达式");
        }
        
        //4.判断是否有“@”
        ind = revision.indexOf("@");
        if (ind != -1)
        {
            throw Asserts.exception("暂不支持[@]表达式");
        }
        
        //5.判断有没有“^”
        ind = revision.indexOf("^");
        if (ind != -1)
        {
            ObjectId oid = resolveIdOrName(revision.substring(0, ind));
            if (oid == null)
                return null;
            
            if (!(revision.charAt(ind+1) == '{' && revision.charAt(revision.length()-1) == '}'))
                throw Asserts.exception("暂不支持[^数字]表达式");
            
            GitWalker walker = new GitWalker(this);
            String type = revision.substring(ind+2, revision.length()-1);
            if (type.equals(""))
            {//any
                return walker.parse(oid);
            }
            else if (type.equals("tree"))
            {//tree
                return walker.parseTree(oid);
            }
            else if (type.equals("commit"))
            {//commit
                return walker.parseCommit(oid);
            }
            else if (type.equals("blob"))
            {//blob
                return walker.parseBlob(oid);
            }
            else
            {//其他的不支持
                throw Asserts.exception("不支持的对象格式："+revision);
            }
        }
        
        //6.最后认为是引用短名称
        Ref ref = refer.findRef(revision);
        return ref == null?null:ref.getObjectId();
    }
    
    /**
     * 读取指定树模型的对象
     * 
     * @param oid           提交ID或树ID
     * @param folder        目录
     * @return              树结构
     * @throws IOException  异常
     */
    public Tree resolveTree(ObjectId oid) throws IOException
    {
        RevTree revTree = resolve(oid, RevTree.class);
        return new Tree().parse(revTree.getContent());
    }
    
    /**
     * 读取树模型对象，并通过目录向下查找
     * 
     * @param idOrName      提交ID或名称，或树ID
     * @param folder        目录
     * @return              树结构
     * @throws IOException  异常
     */
    public Tree resolveTree(ObjectId oid, String folder) throws IOException
    {
        RevTree revTree = resolve(oid, RevTree.class);
        Tree tree = new Tree().setFolder(folder).parse(revTree.getContent());
        if (Validates.isEmpty(folder))
            return tree;
        
        //向下查找指定目录的树
        String nextFolder = folder;
        while (true)
        {
            int ind = nextFolder.indexOf("/");
            if (ind == -1)
            {
                TreeEntry entry = tree.getFolder(nextFolder);
                if (entry == null)
                    return null;
                
                tree = resolveTree(entry.getOid());
                tree.setFolder(folder);
                break;
            }
            
            String curFolder = nextFolder.substring(0, ind);
            TreeEntry entry = tree.getFolder(curFolder);
            if (entry == null)
                return null;
            
            nextFolder = nextFolder.substring(ind+1);
            tree = resolveTree(entry.getOid());
            tree.setFolder(folder);
        }
        
        return tree;
    }
    
    /**
     * 通过提交读取树模型对象，并通过目录向下查找和关联
     * 
     * @param idOrName      提交ID或名称，或树ID
     * @param folder        目录
     * @return              树结构
     * @throws IOException  异常
     */
    public Tree resolveTreeByCommit(RevCommit commit, String folder) throws IOException
    {
        //1.读取树
        Tree tree = resolveTree(commit, folder);
        if (tree == null)
            return null;
        
        //2.绑定提交信息到树中
        resolveTreeBindCommit(commit, tree, folder);
        return tree;
    }
    
    /**
     * 递归绑定文件对应的提交信息
     * 
     * @param git
     * @param fileList
     * @throws IOException 
     */
    private void resolveTreeBindCommit(RevCommit commit, Tree tree, String folder) throws IOException
    {
        List<TreeEntry> list = tree.list();
        while (commit.getParentCount() > 0)
        {
            RevCommit lastCommit = commit;
            commit = commit.getParent(0);
            commit = resolve(commit, RevCommit.class);
            
            Tree commitTree = resolveTree(commit, folder);
            if (commitTree == null)
            {//未找到提交中的树，表示后续的提交中未包含，都设置上次提交并结束
                for (TreeEntry entry : list)
                {
                    if (entry.hasCommit())
                        continue;
                    
                    //如果新的树里没有，则认为是上次提交的
                    entry.setCommit(lastCommit);
                }
                
                return;
            }

            for (TreeEntry entry : list)
            {
                if (entry.hasCommit())
                    continue;
                
                if (commitTree.has(entry.getOid()))
                    continue;
                
                //如果新的树里没有，则认为是上次提交的
                entry.setCommit(lastCommit);
            }
            
            boolean hasNoCommit = false;
            for (TreeEntry entry : list)
            {
                if (entry.hasCommit())
                    continue;
                
                hasNoCommit = true;
                break;
            }
            
            if (!hasNoCommit)
                break;
        }
        
        //循环到最后一次提交，把所有未找到的关联
        for (TreeEntry entry : list)
        {
            if (entry.hasCommit())
                continue;
            
            entry.setCommit(commit);
        }
    }
    
    /************************************************************************************/
    //Git加载对象
    /************************************************************************************/
    
    /**
     * 判断是否存在对象
     * 
     * @param objectId      对象编号
     * @return              =true表示存在
     */
    public boolean hasObject(ObjectId objectId)
    {
        return objer.hasObject(objectId);
    }
    
    /**
     * 获取对象加载器
     * 
     * @param objectId      对象编号
     * @return              得到对象加载器
     * @throws IOException  异常
     */
    public ObjectLoader getObjectLoader(ObjectId objectId) throws IOException
    {
        return objer.getObjectLoader(objectId);
    }
    
    /**
     * 通过对象编号构造该对象文件名
     * 1、先在包中查找，如果有则返回
     * 2、如果对象在共享仓库中，返回该地址
     * 3、如果对象没存储，返回一个可用的文件地址
     * 
     * @param objectId      对象编号
     * @return              测算文件地址
     */
    public File fileForUnpacked(ObjectId objectId)
    {
        return objer.fileForUnpacked(objectId);
    }

    /************************************************************************************/
    //Git引用对象
    /************************************************************************************/

    /**
     * 通过引用全称获取引用对象
     * 
     * @param fullName      引用全称
     * @return              引用对象
     * @throws IOException  异常
     */
    public boolean hasRef(final String fullName)
    {
        try
        {
            return refer.hasRef(fullName);
        }
        catch (IOException e)
        {
            return false;
        }
    }
    
    /**
     * 通过引用全称获取引用对象
     * 
     * @param fullName      引用全称
     * @return              引用对象
     * @throws IOException  异常
     */
    public Ref scanRef(final String fullName) throws IOException
    {
        return refer.scanRef(fullName);
    }
    
    /**
     * 查找引用，通过引用名称全称或短称
     * 
     * @param name          引用名称
     * @return              引用对象
     * @throws IOException  异常
     */
    public Ref findRef(final String name) throws IOException
    {
        return refer.findRef(name);
    }
    
    /** 获取所有引用表 */
    public Map<String, Ref> getRefMap()
    {
        try
        {
            return refer.getRefMap();
        }
        catch (IOException e)
        {
            return new HashMap<String, Ref>();
        }
    }
    
    /** 获取指定前缀的所有引用表 */
    public Map<String, Ref> getRefMap(String prefix)
    {
        try
        {
            return refer.getRefMap(prefix);
        }
        catch (IOException e)
        {
            return new HashMap<String, Ref>();
        }
    }
    
    /** 获取所有引用列表，不包含HEAD */
    public List<ObjectId> getRefObjectIdList() throws IOException
    {
        return refer.getRefObjectIdList();
    }
    
    /** 获取所有引用列表 */
    public List<Ref> getRefList()
    {
        try
        {
            return refer.getRefList();
        }
        catch (IOException e)
        {
            return new ArrayList<Ref>(0);
        }
    }
    
    /** 获取所有标签引用列表 */
    public List<Ref> getTags()
    {
        try
        {
            return refer.getRefList(P_REFS_TAGS_);
        }
        catch (IOException e)
        {
            return new ArrayList<Ref>(0);
        }
    }
    
    /** 
     * 对标签引用进行递归查找到实体引用
     * 
     * @param ref           标签引用
     * @return              非标签引用直接返回本身，标签引用递归查找
     */
    public Ref peel(final Ref ref)
    {
        try
        {
            return refer.peel(ref);
        }
        catch (IOException e)
        {
            return ref;
        }
    }
    
    /** 获取当前仓库分枝引用全称，如：refs/heads/master */
    public String getFullBranch() throws IOException
    {
        Ref head = findRef(HEAD);
        if (head == null)
            return null;
        
        if (head.isSymbolic())
            return head.getTarget().getName();
        
        if (head.getObjectId() != null)
            return head.getObjectId().getName();
        
        return null;
    }

    /** 获取当前仓库分枝引用短称，如：master */
    public String getBranch() throws IOException
    {
        String name = getFullBranch();
        return (name != null)?Gits.shortenRefName(name):null;
    }
    
    /**
     * 发送引用广播
     * 
     * @param out           输出流
     * @param service       服务类型git-receive-pack/git-upload-pack
     * @throws IOException  流异常
     */
    public void sendAdvertisedRefs(OutputStream out, String service) throws IOException
    {
        RefAdvertiser advertiser = new RefAdvertiser(this, out);
        advertiser.sendAdvertisedRef(service);
    }
    
    /************************************************************************************/
    //Git引用更新命令和重命名命令
    /************************************************************************************/
    
    /**
     * 创建一个仓库引用更新器（增加、修改和删除）
     * 
     * @param ref           引用名称
     * @return              引用更新器
     * @throws IOException  异常
     */
    public RefUpdater newUpdater(String ref) throws IOException
    {
        return newUpdater(ref, false);
    }

    /**
     * 创建一个仓库引用更新命令，指定是否独立创建
     * 
     * @param ref           引用名称
     * @param detach        是否独立创建
     * @return              引用更新器
     * @throws IOException  异常
     */
    public RefUpdater newUpdater(String ref, boolean detach) throws IOException
    {
        return refer.newUpdater(ref, detach);
    }

    /**
     * 创建一个仓库引用重命名器
     * 
     * @param fromRefFullName   原引用
     * @param toRefFullName     重命名为新引用
     * @return                  引用重命名命令
     * @throws IOException      异常
     * 
     */
    public RefRenamer newRenamer(String fromRefFullName, String toRefFullName) throws IOException
    {
        return refer.newRenamer(fromRefFullName, toRefFullName);
    }
    
    /*****************************************************************************************/
    //常用命令
    /*****************************************************************************************/
    
    public CommitCommand commit()
    {
        return new CommitCommand(this);
    }
    
    public LogCommand log()
    {
        return new LogCommand(this);
    }
    
    /*****************************************************************************************/
    //分枝命令，列表&创建&删除&重命名
    /*****************************************************************************************/
    
    public BranchListCommand branchList()
    {
        return new BranchListCommand(this);
    }
    
    public BranchCreateCommand branchCreate()
    {
        return new BranchCreateCommand(this);
    }

    public BranchDeleteCommand branchDelete()
    {
        return new BranchDeleteCommand(this);
    }
    
    public BranchRenameCommand branchRename()
    {
        return new BranchRenameCommand(this);
    }

    /*****************************************************************************************/
    //标签命令，列表&更新
    /*****************************************************************************************/
    
    public TagListCommand tagList()
    {
        return new TagListCommand(this);
    }
    
    public TagCommand tag()
    {
        return new TagCommand(this);
    }
    
    public TagDeleteCommand tagDelete()
    {
        return new TagDeleteCommand(this);
    }
    
    public TagRenameCommand tagRename()
    {
        return new TagRenameCommand(this);
    }
    
    /*****************************************************************************************/
    //toString
    /*****************************************************************************************/

    public String toString()
    {
        return "Git[" + gitMetaDir + "]";
    }
}
